﻿"""
Jieun Oh

psychoac2.py -- Jieun's implementation of psychoac.py Music 422 HW4 1d

"""
import numpy as np
#import pylab



def Thresh(f):
     """returns threshold in quiet measured in SPL at frequency f (in Hz)"""
     thresh = 3.64*pow(f*0.001,-0.8) - 6.5*np.exp(-0.6*pow(0.001*f-3.3,2)) + 0.001*pow(0.001*f,4)
     return thresh


def SPL(intensity):
    """ returns the SPL corresponding to intensity (1-->96dB). Note that intensity = |a|^2"""
    return 96 + 10*np.log10(intensity)

def Intensity(spl):
    """ returns the intensity corresponding to SPL (96dB-->1). Note that intensity = |a|^2"""
    return  pow(10, (spl-96)/10.0)

def Bark(f):
    """returns the bark-scale value for input frequency f (in Hz)"""
    return 13*np.arctan(0.00076*f) + 3.5*np.arctan(pow(f/7500.0, 2))

def AssignMDCTLinesFromFreqLimits(nMDCTLines, sampleRate, flimit=np.array([100.0, 200.0, 300.0, 400.0, 510.0, 630.0, 770.0, 920.0, 1080.0, 1270.0, 1480.0, 1720.0, 2000.0, 2320.0, 2700.0, 3150.0, 3700.0, 4400.0, 5300.0, 6400.0, 7700.0, 9500.0, 12000.0, 15500.0, 24000.0])):
    """Assigns MDCT lines to scale factor bands for given sample rate and number of MDCT lines"""
    assign = np.zeros(25)
    mdct_width = sampleRate/ 2.0 / nMDCTLines
    MDCTLines = (np.arange(nMDCTLines) + 0.5)*mdct_width
    
    lb = 0.0   # for flimit
    j = 0      # will increment by 1
    for i in range(len(flimit)):
        #print ("i = ", i)
        #print(lb, MDCTLines[j], flimit[i])
        while (lb < MDCTLines[j] and MDCTLines[j] <= flimit[i]):
            #print(lb, MDCTLines[j], flimit[i])
            assign[i] = assign[i]+1
            #print("MDCT line [", j, "]= ", MDCTLines[j], "was assigned to flimit= ", i, " with ub ", flimit[i])
            j = j+1
            #print ("j= ", j)
            if j== nMDCTLines:
               return np.cast['int'](assign[0:i+1])
        lb = flimit[i]
    return np.cast['int'](assign)



class Masker:
    """a Masker whose masking curve drops linearly in Bark beyond 0.5 Bark from the masker frequency"""
    def __init__(self, f, SPL, isTonal=True):
        """initialized with the frequency and SPL of a masker and whether or not it is Tonal"""
        self.f = f
        self.SPL = SPL
        self.isTonal = True

    def IntensityAtFreq(self, freq):
        """The intensity of this masker at freqnecy freq"""
        return self.IntensityAtBark(Bark(freq))

    def IntensityAtBark(self, z):
        """The intensity of this masker at Bark location z"""
        delta = 15
        dz = z - Bark(self.f)
        level = 0
        if abs(dz)>0.5:
           theta = 1
           if dz<0: theta = 0
           level = (-27+0.37*max(self.SPL-40, 0)*theta)*(abs(dz)-0.5)
        return Intensity(self.SPL-delta+level)


    def vIntensityAtBark(self, zVec):
        """The intensity of this masker at vector of Bark locations zVec"""
        len = np.size(zVec)
        delta = 15
        dzVec = zVec - Bark(self.f)
        levelVec = np.zeros(len)      # to be populated with levels

        sign = np.ones(len)
        sign[dzVec<0] = 0             # vector, elem is 0 if dz is negative, 1 if dz is positive

        flat = abs(dzVec)<=0.5          # vector, true if abs(zVec)<0.5
        not_flat = abs(dzVec)>0.5

        levelVec = (-27+0.37*max(self.SPL-delta-40, 0)*sign)*(abs(dzVec)-0.5) +self.SPL - delta
        levelVec[flat] = self.SPL-delta

        #pylab.plot(zVec, levelVec)
        #pylab.show()

        return Intensity(levelVec)



class ScaleFactorBands:
    """A set of scale factor bands (each of which will share a scale factor and a mantissa bit allocation) and associated MDCT line mappings.Instances know the number of bands nBands; the upper and lower limits for each band lowerLimit[i in range(nBands)], upperLimit[i in range(nBands)];and the number of lines in each band nLines[i in range(nBands)]"""
    def __init__(self, nLines):
        """Assigns MDCT lines to scale factor bands based on a vector of the number of lines in each band"""
        self.nBands = np.size(nLines)
        self.lowerLine = np.zeros(self.nBands)
        self.upperLine = np.zeros(self.nBands)
        self.nLines = np.zeros(self.nBands)
        
        ll = 0
        ul = 0
        for iBand in range(self.nBands):
            if iBand==0:
                ul = max(nLines[0]-1, 0)
            else:
                ul = ul + nLines[iBand]
            self.lowerLine[iBand] = ll
            ll = ll + nLines[iBand]
            self.upperLine[iBand] = ul
            self.nLines[iBand] = self.upperLine[iBand] - self.lowerLine[iBand]+1      

        # note: self.nLines is identical to nLines
        self.lowerLine = np.cast['int'](self.lowerLine)
        self.upperLine = np.cast['int'](self.upperLine)
        self.nLines = np.cast['int'](self.nLines)
        
        # Added by Colin: for compatibility
        self.lowerLimit = self.lowerLine
        self.upperLimit = self.upperLine


# if __name__=="__main__":






